/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * THIS FILE is Public Domain												*
 *																			*
 * fat32.cc																	*
 *																			*
 * Handles FAT32 filesystem reads and writes. Currently also contains		*
 * an implementation of standard functions that should be controlled 		*
 * elsewhere.																*
 *																			*
\***************************************************************************/

/** PUBLIC DOMAIN CODE **/

// disk access
#include <system.h>
#include <disk.h>
#include <filesys.h>
#include <iostream>
#include <memory.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <io.h>

// directory entry types
#define ATTR_READ_ONLY		0x01
#define ATTR_HIDDEN			0x02
#define ATTR_SYSTEM			0x04
#define ATTR_VOLUME_ID		0x08
#define ATTR_DIRECTORY		0x10
#define ATTR_ARCHIVE		0x20
#define ATTR_LONG_NAME		( ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID )
#define ATTR_LONG_NAME_MASK	( ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID | ATTR_DIRECTORY | ATTR_ARCHIVE )

// long directory entry defines
#define LAST_LONG_ENTRY		0x40

// FAT32 BPB starting at offset 36
struct BPB_FAT32
{
	uint_t		BPB_FATSz32;
	ushort_t	BPB_ExtFlags;
	ushort_t	BPB_FsVer;
	uint_t		BPB_RootClus;
	ushort_t	BPB_FsInfo;
	ushort_t	BPB_BkBootSec;
	uchar_t		BPB_Reserved[12];
	uchar_t		BS_DrvNum;
	uchar_t		BS_Reserved1;
	uchar_t		BS_BootSig;
	uint_t		BS_VolID;
	uchar_t		BS_VolLab[11];
	uchar_t		BS_FilSysType[8];
} __attribute((packed));

// information about the filesystem on each drive
struct DriveFileSys
{
	/** FAT **/
	uint_t		DataAreaStart;
	uint_t		RootDirCount;
	uint_t		SectorCount;
} __attribute((packed));

// FAT32 directory entry
struct FAT32_DirEnt {
	uchar_t		DIR_Name[11];
	uchar_t		DIR_Attr;
	uchar_t		DIR_NTRes;
	uchar_t		DIR_CrtTimeTenth;
	ushort_t	DIR_CrtTime;
	ushort_t	DIR_CrtDate;
	ushort_t	DIR_LstAccDate;
	ushort_t	DIR_FstClusHI;
	ushort_t	DIR_WrtTime;
	ushort_t	DIR_WrtDate;
	ushort_t	DIR_FstClusLO;
	uint_t		DIR_FileSize;
};

// FAT32 long directory entry
struct FAT32_LongDirEnt {
	uchar_t		LDIR_Ord;
	uchar_t		LDIR_Name1[10];
	uchar_t		LDIR_Attr;
	uchar_t		LDIR_Type;
	uchar_t		LDIR_Chksum;
	uchar_t		LDIR_Name2[12];
	ushort_t	LDIR_FstClusLO;
	uchar_t		LDIR_Name3[4];
};

// drive information
BPB_FAT drvBPB[32]; // offset 0
BPB_FAT32 drvBPB32[32]; // offset 36 (different data from FAT12/16)
DriveFileSys drvFileSys[32];

// network to host short
#define ntohs(n) ( (((n) & 0xFF00) >> 8) | (((n) & 0x00FF) << 8) )

// host to network short
#define htons(n) ( (((n) & 0xFF00) >> 8) | (((n) & 0x00FF) << 8) )

// network to host long
#define ntohl(n) ( (((n) & 0xFF000000) >> 24) | (((n) & 0x00FF0000) >> 8) | (((n) & 0x0000FF00) << 8) | (((n) & 0x000000FF) << 24) )

// host to network long
#define htonl(n) ( (((n) & 0xFF000000) >> 24) | (((n) & 0x00FF0000) >> 8) | (((n) & 0x0000FF00) << 8) | (((n) & 0x000000FF) << 24) )

// initialize the BPB for a drive (0 is usually IDE master)
void InitBPB_FAT32( int drive )
{
	// buffer
	char buff[512];
	
	// drive name to open
	char* drvname = (char*) kmalloc( 32 );
	sprintf( drvname, "dr%d", drive );
	drive = open( drvname, READONLY );
	kfree( drvname );
	
	// read the data
	lseek( drive, 0, SEEK_SET );
	read( drive, buff, 1 );
	
	// set the data
	drvBPB[drive] = *((BPB_FAT*) buff);
	drvBPB32[drive] = *((BPB_FAT32*) (buff + sizeof( BPB_FAT )));
	
	// get the drive info
	Drive drv = GetDrive( 0 );
	
	// find the data area
	uint_t rootdircount = ( drvBPB[drive].BPB_RootEntCnt * 32 ) + ( drvBPB[drive].BPB_BytsPerSec - 1 ) / drvBPB[drive].BPB_BytsPerSec;
	drvFileSys[drive].DataAreaStart = drvBPB[drive].BPB_RsvdSecCnt + ( drvBPB[drive].BPB_NumFATs * drvBPB32[drive].BPB_FATSz32 ) + rootdircount;
	
	// close the handle
	close( drive );
}

// gets the first sector in a cluster
int FirstSectorOfCluster( int drive, int n )
{		
	// return the value
	return ( ( n - 2 ) * drvBPB[drive].BPB_SecPerClus ) + drvFileSys[drive].DataAreaStart;
}

// gets the offset and sector number of a cluster
void WhereInFat( uint_t drv, uint_t clus, uint_t* sec, uint_t* off )
{
	// variables for later
	uint_t FATOffset = clus * 4;
		
	// sector number and offset in the FAT
	*sec = drvBPB[drv].BPB_RsvdSecCnt + ( FATOffset / drvBPB[drv].BPB_BytsPerSec );
	*off = FATOffset % drvBPB[drv].BPB_BytsPerSec;
}

// gets the value of a cluster entry
uint_t GetClusterEntry( uint_t drv, uint_t clus )
{
	// variables
	uint_t fatsect = 0, offset = 0;
	
	// get where it is in the FAT
	WhereInFat( drv, clus, &fatsect, &offset );

	// entry value to return
	uint_t FatEntryVal = 0;
	
	// FAT sector
	char myfat[512];
	
	// read the sector
	lseek( drv, fatsect, SEEK_SET );
	read( drv, myfat, 1 );
	
	// get the value - top 4 bits are reserved
	FatEntryVal = (*((short*) &myfat[offset])) & 0x0FFFFFFF;
	
	// return it
	return FatEntryVal;
}

// gets the number of clusters at a specific location
// by parsing the FAT
uint_t NumClusters( uint_t drv, uint_t clus )
{
	// count
	uint_t num = 0;
	
	// get the first cluster entry
	uint_t dat = GetClusterEntry( drv, clus );
	
	// read in while not eof
	while( ! ( dat >= 0x0FFFFFF8 ) )
	{
		dat = GetClusterEntry( drv, dat );
		num++;
	}
	
	// all done, return the number of clusters
	return num + 1;
}

/*
// parses a directory string into [drive],[path],[filename]
void ParseDirStr( char* str, char* drv, char* path, char* fn )
{
	//
}


// parse a directory string into [drive],[path],[filename]
void hdd_ParseDirStr( char* str, char* drive, char* path, char* filename )
{
	// string length
	int len = strlen( str );
	
	// find the first slash
	int firstslash = find( str, '/', 0, len );
	
	// put into the drive string
	substr( str, drive, 0, firstslash-1 );
	drive[firstslash] = 0;
	
	// find last slash
	int ls = 0, s = 0;
	while( s != -1 )
	{
		ls = s;
		s = find( str, '/', ls+1, len );
	}
	
	// put into path
	substr( str, path, firstslash+1, ls-1 );
	path[ls-firstslash] = 0;
	
	// finally the filename
	substr( str, filename, ls+1, len );
	filename[(len)-(ls+1)] = 0;
}
*/

// filename buffers
char fatLongFilenames[20][255];
char long_filename[256];
int num_lfn;
int lfn_index = 0;

// adds to the long filenames
void AddLongFilename( char* buff, int i )
{
	// put the data in
	int a,o=0;
	for( a = 1; a < 11; a += 2 )
		fatLongFilenames[lfn_index - 1][o++] = buff[i+a];
	for( a = 14; a < 26; a += 2 )
		fatLongFilenames[lfn_index - 1][o++] = buff[i+a];
	for( a = 28; a < 32; a += 2 )
		fatLongFilenames[lfn_index - 1][o++] = buff[i+a];
}

// searches within a directory, via a buffer
uint_t SearchDir_buff( int drv, int ata_off, uchar_t* buff, int len, char* fn )
{
	// set the long filename index
	lfn_index = 0;
	
	// the next one is the one to get the etry for
	int isnext = 0;
	
	// get info
	for( int i = 0; i < len; i += 32 )
	{
		// check for a valid file
		if( buff[i] == 0xE5 )
		{
			continue;
		}
		if( buff[i] == 0x00 )
		{
			break;
		}
		
		// get the data of this entry
		FAT32_DirEnt myEnt = *((FAT32_DirEnt*) &buff[i]);
		
		// this is not how to handle long directory entries!
		if( myEnt.DIR_Attr == ATTR_LONG_NAME )
		{
			// this is a long directory name entry
			
			// is this the last entry?
			// there can only possibly be 0x18 entries, (260 / 13 = 20d [0x18])
			if( ( myEnt.DIR_Name[0] ^ 0x40 ) < 0x18 )
			{
				// set the index
				lfn_index = num_lfn = myEnt.DIR_Name[0] ^ 0x40;
				
				// add the filename
				AddLongFilename( (char*) buff, i );
			}
			else
			{
				// check if this one is the last set
				if( --lfn_index == 1 )
				{
					// add the filename
					AddLongFilename( (char*) buff, i );
					
					// buffer for the full long filename
					char* lfnbuff = (char*) kmalloc( num_lfn * 255 );
					int off = 0;
					for( int y = 0; y < num_lfn; y++ )
					{
						for( int z = 0; z < strlen( fatLongFilenames[y] ); z++ )
						{
							lfnbuff[off++] = fatLongFilenames[y][z];
						}
					}
					
					// check against the filename
					if( strcmp( (char*) lfnbuff, fn, strlen( fn ) ) )
					{
						isnext = 1;
					}
					
					// free the buffer
					kfree( lfnbuff );
				}
				else
				{
					// add the filename
					AddLongFilename( (char*) buff, i );
				}
			}
			
			// skip the printing
			continue;
		}
		
		// check if we need this one
		if( isnext )
		{
			// return the data
			return ( myEnt.DIR_FstClusHI << 16 ) | myEnt.DIR_FstClusLO;
		}
		else
		{
			if( myEnt.DIR_Name[0] == '.' ) { continue; };
			if( strcmp( (char*) myEnt.DIR_Name, fn, 11 ) )
			{
				return ( myEnt.DIR_FstClusHI << 16 ) | myEnt.DIR_FstClusLO;
			}
		}
	}
	return 0;
}

// searches within a directory, given the first cluster for it
uint_t SearchDir( int drive, int ata_off, char* fn, int dir_clus )
{
	// find the sector number
	uint_t dirsect = FirstSectorOfCluster( ata_off, dir_clus );
	
	// the number of sectors
	uint_t numsect = NumClusters( ata_off, dir_clus ) * drvBPB[ata_off].BPB_SecPerClus;
	
	// number of bytes
	uint_t numbytes = numsect * drvBPB[ata_off].BPB_BytsPerSec;
	
	// read it in (only one cluster, this doesn't use the FAT - yet)
	uchar_t* buff = (uchar_t*) kmalloc( numbytes );
	int secoff = 0;
	lseek( drive, dirsect, SEEK_SET );
	while( secoff < numbytes )
	{
		read( drive, (void*) (buff+secoff), 1 );
		secoff += 512;
	}
	
	// return value
	uint_t ret = SearchDir_buff( drive, ata_off, buff, numbytes, fn );
	
	// free the buffer
	kfree( buff );
	
	// return what we found
	return ret;
}

// creates a buffer for the entire directory, and reads the data in
uchar_t* ReadDir( int drive, int ata_off, uint_t dir_clus )
{
	// find the sector number
	uint_t dirsect = FirstSectorOfCluster( ata_off, dir_clus );
	
	// the number of sectors
	uint_t numsect = NumClusters( ata_off, dir_clus ) * drvBPB[ata_off].BPB_SecPerClus;
	
	// number of bytes
	uint_t numbytes = numsect * drvBPB[ata_off].BPB_BytsPerSec;
	
	// read it in (only one cluster, this doesn't use the FAT - yet)
	uchar_t* buff = (uchar_t*) kmalloc( numbytes );
	int secoff = 0;
	while( secoff < numbytes )
	{
		read( drive, (void*) (buff+secoff), 1 );
		secoff += 512;
	}

	// return the buffer
	return buff;
}

// searches the root directory for a given filename
uint_t SearchRootDir( int drive, int ata_off, char* fn )
{	
	// return value
	uint_t ret = SearchDir( drive, ata_off, fn, drvBPB32[ata_off].BPB_RootClus ); // SearchDir_buff( drive, buff, numsect * drvBPB[drive].BPB_BytsPerSec, fn );

	// return the location
	return ret;
}

// converts a character to uppercase (only alpha, and only lowercase characters)
char toupper( char c )
{
	 if( c >= 'a' && c <= 'z' )
	 {
		c += ( 'A' - 'a' );
	}
	return c;
}

// pads a short filename (and will just return the filename if it doesn't need to be padded)
uchar_t* PadFilename( uchar_t* str )
{
	// check, is it more than 11 bytes?
	if( strlen( (char*) str ) > 11 )
	{
		// long filename!
		return str;
	}
	
	// counters and the temp string (that will hold	the new filename)
	uint_t count = 0, tmp_count = 0;
	uchar_t tmpstring[11];
	
	// loop through each character in the string
	for( tmp_count = 0; tmp_count < 11; tmp_count++, count++ )
	{
		// if it's not a dot, put it in the string
		if( str[count] != '.' && str[count] != 0 )
		{
			tmpstring[tmp_count] = toupper( str[count] );
		}
		else
		{
			// space out the string
			for( ; tmp_count < 8; tmp_count++ )
				tmpstring[tmp_count] = ' ';
			
			// put in the extension
			if( str[count+1] != 0 )
			{
				tmpstring[tmp_count] = toupper( str[count+1] );
				tmpstring[tmp_count+1] = toupper( str[count+2] );
				tmpstring[tmp_count+2] = toupper( str[count+3] );
			}
			else
			{
				tmpstring[tmp_count] = ' ';
				tmpstring[tmp_count+1] = ' ';
				tmpstring[tmp_count+2] = ' ';
			}
			
			// break from the main loop
			break;
		}
	}
	
	// copy the temp string into the main one
	tmpstring[11] = 0;
	memcpy( str, tmpstring, 12 );
	
	// return it
	return str;
}

// parses a directory string into the drive, the path and the filename
void ParseDirStr( char* str, char* drive, char* path, char* filename )
{
	// get the length of the string
	int len = strlen( str );
	
	// find the first slash
	int firstslash = find( str, '/', 0, len );
	
	// put this into the drive part
	substr( drive, str, 0, 3 );
	drive[4] = 0;
	
	// find the last slash
	int ls = 0, s = 0;
	while( s != -1 )
	{
		ls = s;
		s = find( str, '/', ls + 1, len );
	}
	
	// put into the path
	substr( path, str, firstslash + 1, ls );
	path[ls-firstslash] = 0;
	
	// get the filename
	substr( filename, str, ls + 1, len );
	filename[ ( len ) - ( ls + 1 ) ] = 0;
}

// prints out the contents of the root directory
void PrintRootDir_FAT32( int drive )
{
	// find the root directory
	uint_t rootdir = FirstSectorOfCluster( drive, drvBPB32[drive].BPB_RootClus );
	
	// the number of sectors
	uint_t numsect = NumClusters( drive, drvBPB32[drive].BPB_RootClus ) * drvBPB[drive].BPB_SecPerClus;
	
	// read it in
	uchar_t* buff = (uchar_t*) kmalloc( numsect * drvBPB[drive].BPB_BytsPerSec );
	lseek( drive, rootdir, SEEK_SET );
	read( drive, buff, numsect );
	
	// set the long filename index
	lfn_index = 0;
	
	// get info
	for( int i = 0; i < 1024; i += 32 )
	{
		// check for a valid file
		if( buff[i] == 0xE5 )
			continue;
		if( buff[i] == 0x00 )
			break;
		
		// get the data of this entry
		FAT32_DirEnt myEnt = *((FAT32_DirEnt*) &buff[i]);
		
		// this is not how to handle long directory entries!
		if( myEnt.DIR_Attr == ATTR_LONG_NAME )
		{
			// this is a long directory name entry
			
			// is this the last entry?
			// there can only possibly be 0x18 entries, (260 / 13 = 20d [0x18])
			if( ( myEnt.DIR_Name[0] ^ 0x40 ) < 0x18 )
			{
				// set the index
				lfn_index = num_lfn = myEnt.DIR_Name[0] ^ 0x40;
				
				// add the filename
				AddLongFilename( (char*) buff, i );
			}
			else
			{
				// check if this one is the last set
				if( --lfn_index == 1 )
				{
					// add the filename
					AddLongFilename( (char*) buff, i );
					
					// print them
					for( int z = 0; z < num_lfn; z++ )
					{
						cout << (char*) fatLongFilenames[z];
					}
					cout << endl;
				}
				else
				{
					// add the filename
					AddLongFilename( (char*) buff, i );
				}
			}
			
			// skip the printing
			continue;
		}
		
		// print the file info
		myEnt.DIR_Name[11] = 0;
		cout << (char*) myEnt.DIR_Name << "\t";
		uint_t clust = ( myEnt.DIR_FstClusHI << 16 ) | myEnt.DIR_FstClusLO;
		cout << " at " << FirstSectorOfCluster( drive, clust ) << " [" << clust << "], ";
		cout << myEnt.DIR_FileSize << " bytes" << ", ";
		cout << NumClusters( drive, clust ) << " clusters" << endl;
	}
	
	// free the buffer
	kfree( buff );
}

// a null file
FILE nullfile = { 0 };

// closes a file
int fclose( FILE fp )
{
	// check for a valid file
	if( fp.clus == 0 )
		return -1;
	
	// close the descriptor
	return close( fp.dev );
}

// ascii --> integer
int atoi( char* ptr )
{
	// base and current number
	int base = 1;
	int curr = 0;
	
	// loop through, add them all up
	for( int i = 0; i < strlen( ptr ); i++ )
	{
		if( ptr[i] >= '0' && ptr[i] <= '9' )
		{
			curr += (ptr[i] - '0') * base;
			base *= 10;
		}
	}
	
	// return the number
	return curr;
}

// finds and opens a file
FILE fopen( const char* fn, const char* access )
{
	// firstly, get each different part of the filename - this means file paths can be a max of 514 characters long
	char* drv = (char*) kmalloc( 4 );
	char* path = (char*) kmalloc( 255 );
	char* filename = (char*) kmalloc( 255 );
	
	// parse the string
	ParseDirStr( const_cast< char* >( fn ), drv, path, filename );
	
	kprintf( "Drive: %s\nPath: %s\nFilename: %s\n", drv, path, filename );

	// look for stuff in the access string
	int read_bit = find( access, 'r', 0, strlen( access ) );
	int write_bit = find( access, 'w', 0, strlen( access ) );
	
	// what mode?
	int mode = 0;
	if( read_bit )
		mode |= READONLY;
	if( write_bit )
		mode |= WRITEONLY;
	
	// if we don't have a mode, we say oops
	if( !mode )
	{
		kprintf( "No mode bits!\n" );
		return nullfile;
	}

	// open the drive
	uint_t drive = open( drv, mode );
	
	// find out which drive it was (for ata_off)
	char* tmpdrv = (char*) kmalloc( 32 );
	substr( tmpdrv, drv, 2, strlen( drv ) );
	tmpdrv[strlen(drv)-2] = 0;
	
	// convert tmpdrv to an integer
	int ata_off = atoi( tmpdrv );
	
	// free the memory taken
	kfree( tmpdrv );
	
	// now split the path by slash and search for each directory
	uint_t clus = 0; uint_t curr = 0; uint_t last = 0; uint_t pathlen = strlen( path );
	
	// check if there is in fact no path
	if( pathlen == 0 )
	{
		clus = drvBPB32[ata_off].BPB_RootClus;
	}
	else
	{
		// allocate the maximum path space
		char* buff = (char*) kmalloc( 255 );
		
		// wait until there are no more slashes found
		while( curr != -1 )
		{
			// find the next, and save the current one
			last = curr;
			curr = find( path, '/', last, pathlen );
			
			// if there is no more, get from the last slash
			// to the end of the path
			if( curr == -1 )
			{
				substr( buff, path, last, pathlen );
				buff[(pathlen-1)-last] = 0;
			}
			// otherwise, go from the last to this one
			else
			{
				substr( buff, path, last, curr );
				buff[(curr-1)-last] = 0;
			}
	
			// pad out the path
			buff = (char*) PadFilename( (uchar_t*) buff );
			
			// if the cluster is 0, we haven't done any searching yet, so search
			// the root directory
			if( clus == 0 )
				clus = SearchRootDir( drive, ata_off, buff );
			// otherwise, search in the last directory
			else
				clus = SearchDir( drive, ata_off, buff, clus );
		}
		
		// free the buffer
		kfree( buff );
	}
	
	// valid?
	if( clus == 0 )
	{
		// free all the memory
		kfree( drv );
		kfree( path );
		kfree( filename );
		
		// close the descriptor
		close( drive );
		
		// eek! invalid cluster!
		return nullfile;
	}
	
	// pad out the filename
	char* fbuff;
	fbuff = (char*) PadFilename( (uchar_t*) filename ); /** just figured it out... **/
	
	// we now have a cluster to search in
	clus = SearchDir( drive, ata_off, fbuff, clus );
	
	// valid?
	if( clus == 0 )
	{
		// free all the memory
		kfree( drv );
		kfree( path );
		kfree( filename );
		
		// close the descriptor
		close( drive );
		
		// eek! invalid cluster!
		return nullfile;
	}
	
	// good to go, build a return structure
	FILE ret;
	ret.clus = clus;
	ret.devdesc = drive;
	ret.dev = ata_off;
	ret.size = NumClusters( ata_off, clus ) * drvBPB[ata_off].BPB_SecPerClus * drvBPB[ata_off].BPB_BytsPerSec;
	
	// free all the memory
	kfree( drv );
	kfree( path );
	kfree( filename );
	
	// return it
	return ret;
}

// writes data to a file
uint_t fwrite( void* buff, uint_t offset, uint_t count, FILE fp )
{
	// check for a valid file
	if( fp.clus == 0 )
		return -1;
	
	/*************** NOT COMPLETE YET!!! ***************/
	
	// we need to firsly check for a sector boundary break
	
	// the final offset into the file
	uint_t totbytes = offset + count;
	
	// the sector of the offset's sector
	uint_t offsect = offset / drvBPB[fp.dev].BPB_BytsPerSec;
	
	// sector of the final offset
	uint_t allsect = totbytes / drvBPB[fp.dev].BPB_BytsPerSec;
	
	// number of bytes read
	uint_t nb = 0;

	// find out which sector (non-relative)
	uint_t realsect = FirstSectorOfCluster( fp.dev, fp.clus ) + offsect;
	
	// failed
	return 0;
}

// reads an arbirtary number of bytes from a file
uint_t fread( void* dest, uint_t offset, uint_t count, FILE fp )
{
	// check for a valid file
	if( fp.clus == 0 )
		return -1;
	
	// we need to firsly check for a sector boundary break
	
	// the final offset into the file
	uint_t totbytes = offset + count;
	
	// the sector of the offset's sector
	uint_t offsect = offset / drvBPB[fp.dev].BPB_BytsPerSec;
	
	// sector of the final offset
	uint_t allsect = totbytes / drvBPB[fp.dev].BPB_BytsPerSec;
	
	// number of bytes read
	uint_t nb = 0;

	// find out which sector (non-relative)
	uint_t realsect = FirstSectorOfCluster( fp.dev, fp.clus ) + offsect;
	
	// are they equal?
	if( offsect == allsect )
	{		
		// find which cluster it's in
		uint_t rel_clust = offsect / drvBPB[fp.dev].BPB_SecPerClus;
		
		// the first cluster
		if( rel_clust == 0 )
		{
			// sector buffer
			char* buff = (char*) kmalloc( drvBPB[fp.dev].BPB_BytsPerSec );
			
			// read it in
			lseek( fp.devdesc, realsect, SEEK_SET );
			read( fp.devdesc, buff, 1 );
			
			// setup a char pointer to the destination data
			char* dest_cpy = (char*) dest;
			
			// do the dirty deed
			for( int i = offset; i < totbytes; i++ )
			{
				dest_cpy[nb++] = buff[i];
			}
			
			// free the buffer
			kfree( buff );
			
			// return the number of bytes read
			return nb;
		}
		else
		{
			// another cluster, so we have to look at the FAT
			
			// cluster info array
			int clustinfo[1024];
			
			// running cluster accumulator
			uint_t clus;
			
			// running counter for the array
			uint_t index = 0;
			
			// set the first cluster
			clustinfo[index++] = fp.clus;

			// get the NEXT cluster's info, this means that in the loop the call to
			// GetClusterEntry will return the DATA of this cluster index
			clus = GetClusterEntry( fp.dev, fp.clus );
			
			// get the next cluster's info, until we reach the end
			while( ! ( clus >= 0x0FFFFFF8 ) )
			{
				// set this cluster
				clustinfo[index++] = clus;
				
				// get the data at this entry
				clus = GetClusterEntry( fp.dev, clus );
			}
			
			// now get the sector to read
			uint_t sec = FirstSectorOfCluster( fp.dev, clustinfo[rel_clust] ) + ( offsect % drvBPB[fp.dev].BPB_SecPerClus );
			
			// sector buffer
			char* buff = (char*) kmalloc( drvBPB[fp.dev].BPB_BytsPerSec );
			
			// read it in
			lseek( fp.devdesc, sec, SEEK_SET );
			read( fp.devdesc, buff, 1 );
			
			// setup a char pointer to the destination data
			char* dest_cpy = (char*) dest;
			
			// do the dirty deed
			for( int i = ( offset % (drvBPB[fp.dev].BPB_BytsPerSec) ); i < ( offset % (drvBPB[fp.dev].BPB_BytsPerSec) ) + count; i++ )
			{
				dest_cpy[nb++] = buff[i];
			}
			
			// free the buffer
			kfree( buff );
			
			// return the number of bytes
			return nb;
		}
	}
	else
	{		
		// check for a cluster jump
		uint_t rel_clust_off = offsect / drvBPB[fp.dev].BPB_SecPerClus;
		uint_t rel_clust_tot = allsect / drvBPB[fp.dev].BPB_SecPerClus;
		
		// equal?
		if( rel_clust_off == rel_clust_tot )
		{
			// find out which cluster
			if( rel_clust_off == 0 )
			{
				// find out how many sectors
				uint_t numsect = ( allsect - offsect ) + 1;
				
				// allocate a buffer
				char* buff = (char*) kmalloc( drvBPB[fp.dev].BPB_BytsPerSec * numsect );
			
				// setup a char pointer to the destination data
				char* dest_cpy = (char*) dest;
				
				// fill it
				lseek( fp.devdesc, realsect, SEEK_SET );
				read( fp.devdesc, buff, numsect );
				
				int i;
				
				// now fill the destination data
				for( i = offset; i < totbytes; i++ )
				{
					dest_cpy[nb++] = buff[i];
				}
				
				// free the buffer
				kfree( buff );
				
				// return the number of bytes read
				return nb;
			}
			else
			{
				// another cluster, so we have to look at the FAT
				// different from previous because this is over x amount
				// of sectors
				
				// cluster info array
				int clustinfo[1024];
				
				// running cluster accumulator
				uint_t clus;
				
				// running counter for the array
				uint_t index = 0;
				
				// set the first cluster
				clustinfo[index++] = fp.clus;

				// get the NEXT cluster's info, this means that in the loop the call to
				// GetClusterEntry will return the DATA of this cluster index
				clus = GetClusterEntry( fp.dev, fp.clus );
				
				// get the next cluster's info, until we reach the end
				while( ! ( clus >= 0x0FFFFFF8 ) )
				{
					// set this cluster
					clustinfo[index++] = clus;
					
					// get the data at this entry
					clus = GetClusterEntry( fp.dev, clus );
				}
				
				// now get the sector to read
				uint_t sec = FirstSectorOfCluster( fp.dev, clustinfo[rel_clust_off] ) + ( offsect % drvBPB[fp.dev].BPB_SecPerClus );
				
				// find out how many sectors
				uint_t numsect = ( allsect - offsect ) + 1;
				
				// sector buffer
				char* buff = (char*) kmalloc( numsect * drvBPB[fp.dev].BPB_BytsPerSec );
				
				// read it in
				lseek( fp.devdesc, sec, SEEK_SET );
				read( fp.devdesc, buff, numsect );
				
				// setup a char pointer to the destination data
				char* dest_cpy = (char*) dest;
				
				// do the dirty deed
				for( int i = ( offset % (numsect*drvBPB[fp.dev].BPB_BytsPerSec) ); i < ( offset % (numsect*drvBPB[fp.dev].BPB_BytsPerSec) ) + count; i++ )
				{
					dest_cpy[nb++] = buff[i];
				}
				
				// free the buffer
				kfree( buff );
				
				// return the number of bytes
				return nb;
			}
		}
		else
		{
			// spans across 2 (or more!) clusters
			
			// create storage for all the clusters
			int clustinfo[1024];
			
			// the running cluster counters
			uint_t clus;
			
			// running counter for the array
			uint_t index = 0;
			
			// we know what the first cluster is
			clustinfo[index++] = fp.clus;
			
			// get the NEXT cluster's info, this means that in the loop the call to
			// GetClusterEntry will return the DATA of this cluster index
			clus = GetClusterEntry( fp.dev, fp.clus );
			
			// get the next cluster's info, until we reach the end
			while( ! ( clus >= 0x0FFFFFF8 ) )
			{
				// set this cluster
				clustinfo[index++] = clus;
				
				// get the data at this entry
				clus = GetClusterEntry( fp.dev, clus );
			}
			
			// we now have an array of all the clusters this file takes
			// now this has to be used to read in the data - we need to
			// find out the sectors to be read
			
			// ok, rel_clust_off and rel_clust_tot hold the relative clusters for each sector
			// we need to find out how many clusters to read
			uint_t numclust = ( rel_clust_tot - rel_clust_off ) + 1;	// ALWAYS read in one too many, just in case the
																		// last byte read happens to be in the last cluster
																		// and we didn't pick it up
			
			// buffer for all the data
			char* fbuff = (char*) kmalloc( numclust * drvBPB[fp.dev].BPB_SecPerClus * drvBPB[fp.dev].BPB_BytsPerSec );
			
			// how many sectors, this is decremented as we go
			uint_t numsect = allsect - offsect;
			
			// and this one isn't decremented
			uint_t totsect = numsect;
			
			// sector read count
			uint_t sec_count = 0;
			
			// now we read in for each cluster
			int i;
			for( i = 0; i < numclust; i++ )
			{
				// the starting sector for this cluster
				uint_t startsec = FirstSectorOfCluster( fp.dev, clustinfo[i] );
				
				// we'll need to increment the offset sector if this is the first time
				if( i == 0 )
					startsec += offsect;
				
				// and then for each sector
				lseek( fp.devdesc, startsec, SEEK_SET );
				read( fp.devdesc, (void*) (fbuff + (sec_count*drvBPB[fp.dev].BPB_BytsPerSec)), drvBPB[fp.dev].BPB_SecPerClus );
				sec_count += drvBPB[fp.dev].BPB_SecPerClus;
			}
			
			// setup a char pointer to the destination data
			char* dest_cpy = (char*) dest;
				
			// now (finally) return the data
			for( i = offset; i < (offset+count); i++ )
			{
				dest_cpy[nb++] = fbuff[i];
			}
			
			// free the file buffer
			kfree( fbuff );
			
			// return the number of bytes
			return nb;
		}
	}
	
	// nothing!
	return 0;
}
